import os
import subprocess
import signal
import traceback
import random
import time
import logging
import multiprocessing
from abc import ABCMeta, abstractmethod


class Worker(object):
    __metaclass__ = ABCMeta

    def __init__(self, test):
        self.test = test
        self.name = self.test.case.name
        self.log_file = '%s-%s.log' % (self.name, str(random.randint(10000, 99999)))
        self.log_relative = os.path.join(self.test.name, self.log_file)
        self.log = os.path.join(self.test.logdir, self.log_file)
        self.start_time = ''
        self.end_time = ''
        self.timeout = self.test.case.runtime + 120
        self.proc = None

    @abstractmethod
    def submit(self):
        pass

    @abstractmethod
    def kill(self):
        pass

    @abstractmethod
    def is_subprocess_alive(self):
        pass

    @abstractmethod
    def result(self):
        pass

    def get_exit_msg(self):
        if self.result() == 0:
            return ''
        break_loop = False
        for line in open(self.log, 'r'):
            if break_loop:
                return line
            if '%s test FAIL!' % self.test.case.name in line or \
                '%s test SKIP!' % self.test.case.name in line:
                break_loop = True
        return ''

    def close_fd(self):
        pass

    def run(self):
        self.start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        self.submit()
        start_time = time.time()
        while True:
            time.sleep(5)
            if time.time() - start_time > self.timeout:
                self.kill()
                logging.error('testcase %s does not finish in %s secs, killed it...' % (self.name, self.timeout))
                break
            if self.is_subprocess_alive():
                continue
            break
        self.end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        self.close_fd()
        return self.result()


class PythonWorker(Worker):
    @staticmethod
    def run_python_case(workdir, logfile, pythonlib, properties, env):
        import sys
        import os
        os.chdir(workdir)
        fd = open(logfile, 'w+', 1)
        sys.stdout = sys.stderr = fd
        # update env
        for te_k, te_v in env.items():
            os.environ[te_k] = te_v
        try:
            sys.path.append(os.getcwd())
            loc = locals()
            exec('from %s import run_test' % pythonlib)
            exitcode = loc['run_test'](properties)
            sys.exit(exitcode)
        except Exception as e:
            print(e)
            fd.write(traceback.format_exc())
            fd.flush()
            fd.close()
            sys.exit(1)

    def submit(self):
        self.proc = multiprocessing.Process(target=self.run_python_case,
                                            args=(self.test.cwd, self.log, self.test.cmd,
                                                  self.test.device.properties, self.test.env))
        self.proc.start()

    def kill(self):
        if self.proc.is_alive():
            logging.error('Killing the PID %s' % str(self.proc.pid))
            self.proc.terminate()

    def is_subprocess_alive(self):
        return self.proc.is_alive()

    def result(self):
        return self.proc.exitcode


class ShellWorker(Worker):
    def __init__(self, test):
        super(ShellWorker, self).__init__(test)
        self.stdout = open(self.log, 'w+b', 0)
        self.stdin = open('/dev/zero', 'rb')

    def submit(self):
        self.proc = subprocess.Popen(self.test.cmd, shell=True, stdin=self.stdin,
                                     stdout=self.stdout, stderr=subprocess.STDOUT,
                                     close_fds=True, env=self.test.env,
                                     preexec_fn=os.setpgrp, cwd=self.test.cwd)

    def kill(self):
        if self.proc and self.proc.poll() is None:
            try:
                logging.error('Killing the PID %s' % str(self.proc.pid))
                os.killpg(self.proc.pid, signal.SIGKILL)
            except Exception as e:
                print(e)
                pass

    def is_subprocess_alive(self):
        return self.proc.poll() is None

    def result(self):
        return self.proc.returncode

    def close_fd(self):
        self.stdout.close()
        self.stdin.close()


def select_worker(test):
    w = {'shell': ShellWorker, 'python': PythonWorker}
    return w[test.case.filetype](test)
